package lumis.portal.dao.hibernate;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Set;

import net.sf.beanlib.BeanlibException;
import net.sf.beanlib.PropertyInfo;
import net.sf.beanlib.hibernate3.Hibernate3JavaBeanReplicator;
import net.sf.beanlib.spi.BeanTransformerSpi;
import net.sf.beanlib.spi.replicator.BeanReplicatorSpi;

/**
 * A replicator for hibernate beans, based on replicating fields directly.
 *
 * @version $Revision: 10825 $ $Date: 2009-08-04 12:36:40 -0300 (Tue, 04 Aug 2009) $
 * @since 5.0.0
 */
public class Hibernate3FieldReplicator extends Hibernate3JavaBeanReplicator
{
	/**
	 * Factory for {@link Hibernate3FieldReplicator}.
	 *
	 * @version $Revision: 10825 $ $Date: 2009-08-04 12:36:40 -0300 (Tue, 04 Aug 2009) $
	 * @since 5.0.0
	 */
	public static class Factory implements BeanReplicatorSpi.Factory
	{
		private final Set<Field> fieldsToIgnore;

		/**
		 * Creates a new factory for {@link Hibernate3FieldReplicator}.
		 * @param fieldsToIgnore the set of fields that should not be 
		 * replicated.
		 * @since 5.0.0
		 */
		public Factory(Set<Field> fieldsToIgnore)
		{
			this.fieldsToIgnore = fieldsToIgnore;
		}

		public Hibernate3FieldReplicator newBeanReplicatable(BeanTransformerSpi beanTransformer)
		{
			return new Hibernate3FieldReplicator(beanTransformer, fieldsToIgnore);
		}

	}

    private final BeanTransformerSpi transformer;
    private final Set<Field> fieldsToIgnore;
    
    protected Hibernate3FieldReplicator(BeanTransformerSpi beanTransformer, Set<Field> fieldsToIgnore)
    {
        super(beanTransformer);
        transformer = beanTransformer;
        this.fieldsToIgnore = fieldsToIgnore;
    }

    @Override
	protected void populateBean(Object fromMember, Object toMember)
	{
    	populate(fromMember, toMember);
	}

    /** 
     * Populates an object's fields with values retrieved from fields 
     * of another object.
     * @param fromObj the object from where the values will be read.
     * @param toObj the object where the values will be written to.
	 * @since 5.0.0
     */
    public void populate(Object fromObj, Object toObj) 
    {
        if (transformer != null)
        	transformer.getClonedMap().put(fromObj, toObj);
        
        // invoking all declaring setter methods of toBean from all matching getter methods of fromBean
        for (Field toField: toObj.getClass().getDeclaredFields())
        {
        	int mod = toField.getModifiers();
			if (!Modifier.isStatic(mod) && !Modifier.isFinal(mod) && 
					!Modifier.isTransient(mod) && !fieldsToIgnore.contains(toField))
			{
				processField(fromObj, toObj, toField);
			}
        }
    }
    
	/**
     * Processes a specific setter method for the toBean.
     * @param fromObj the object from where the values will be read.
     * @param toObj the object where the values will be written to.
     * @param toField a specific field of <code>toObj</code>.
	 * @since 5.0.0
     */
    private void processField(Object fromObj, Object toObj, Field toField)
    {
        String fieldName = toField.getName();
        
        Field fromField;
        try
        {
    		fromField = fromObj.getClass().getDeclaredField(fieldName);
        }
        catch(NoSuchFieldException e)
        {
        	return;
        }
        
        // read value
        Class<?> paramType = toField.getType();
        Object propertyValue = readFieldAsPrivileged(fromObj, fromField);
		
        // transform value
		if (transformer != null) 
		{
		    PropertyInfo propertyInfo = new PropertyInfo(fieldName, fromObj, toObj);
		    propertyValue = transformer.transform(propertyValue, paramType, propertyInfo);
		}
		
		// write value  
		writeFieldAsPrivileged(toObj, toField, propertyValue);
    }

    /** 
     * Reads the field value, as a privileged action if necessary. 
	 * @since 5.0.0
     */
    private Object readFieldAsPrivileged(final Object target, final Field field)
    {
    	if (Modifier.isPublic(field.getModifiers()))
		{
			try
			{
				return field.get(target);
			}
			catch (IllegalAccessException ex)
			{
				// drop thru to try again
			}
		}
		return AccessController.doPrivileged(new PrivilegedAction<Object>()
		{
			public Object run()
			{
				field.setAccessible(true);
				try
				{
					return field.get(target);
				}
				catch (IllegalArgumentException e)
				{
					throw new BeanlibException(e);
				}
				catch (IllegalAccessException e)
				{
					throw new BeanlibException(e);
				}
			}
		});
    }
    /** 
     * Writes the field value, as a privileged action if necessary.
	 * @since 5.0.0
     */
    private void writeFieldAsPrivileged(final Object target, final Field field, final Object value)
    {
        if (Modifier.isPublic(field.getModifiers()))
		{
			try
			{
				field.set(target, value);
			}
			catch (IllegalAccessException ex)
			{
				// drop thru to try again
			}
		}
		AccessController.doPrivileged(new PrivilegedAction<Object>()
		{
			public Object run()
			{
				field.setAccessible(true);
				try
				{
					field.set(target, value);
					return null;
				}
				catch (IllegalArgumentException e)
				{
					throw new BeanlibException(e);
				}
				catch (IllegalAccessException e)
				{
					throw new BeanlibException(e);
				}
			}
		});
    }
}